/* 
   Unix SMB/Netbios implementation.
   Version 1.5.
   Copyright (C) Andrew Tridgell 1992,1993,1994
   
   This program is free software; you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation; either version 2 of the License, or
   (at your option) any later version.
   
   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.
   
   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

#include "includes.h"

pstring debugf = DEBUGFILE;

pstring domain="";

char *InBuffer = NULL;
char *OutBuffer = NULL;

extern BOOL NeedSwap;

BOOL reply_only = False;

extern struct in_addr lastip;
extern struct in_addr myip;
extern struct in_addr bcast_ip;
pstring myname="";
pstring myhostname="";
int myttl = 0;

typedef struct
{
  BOOL valid;
  pstring name;
  struct in_addr ip;
  int ttl;
  unsigned char nb_flags;
} name_struct;

int num_names=0;
name_struct *names = NULL;

void construct_reply(char *,char *);

/* are we running as a daemon ? */
BOOL daemon = False;


BOOL got_bcast = False;

/****************************************************************************
add a netbios name
****************************************************************************/
int add_name(void)
{
  int i;
  name_struct *n;

  for (i=0;i<num_names;i++)
    if (!names[i].valid)
      return(i);

  if (num_names == 0)    
    n = (name_struct *)malloc(sizeof(name_struct));
  else
    n = (name_struct *)realloc(names,sizeof(name_struct)*(num_names+1));
  if (!n) 
    {
      Debug(0,"Can't malloc more names space!\n");
      return(-1);
    }
  n[num_names].valid = False;
  names = n;
  num_names++;
  return(num_names-1);
}

/****************************************************************************
delete a netbios name
****************************************************************************/
void del_name(int i)
{
  names[i].valid = False;
}

/****************************************************************************
find a name
****************************************************************************/
int find_name(char *s)
{
  int i;
  for (i=0;i<num_names;i++)
    if (names[i].valid && name_equal(s,names[i].name))
      return(i);
  return -1;
}

/****************************************************************************
true if two IP addresses are equal
****************************************************************************/
BOOL ip_equal(struct in_addr *ip1,struct in_addr *ip2)
{
  char *p1=(char *)ip1;
  char *p2=(char *)ip2;
  int l = sizeof(*ip1);
  while (l--)
    if (*p1++ != *p2++)
      return(False);
  return(True);
}

/****************************************************************************
register a name on the net
****************************************************************************/
BOOL register_name(char *inbuf,char *outbuf,char *name,struct in_addr *ip)
{
  unsigned char nb_flags = 0;
  int count;
  static uint16 name_trn_id = 0x4242;
  char *p;

  Debug(1,"Registering name %s (%s) nb_flags=0x%x\n",
	name, inet_ntoa(*ip) ,nb_flags);

  SSVAL(outbuf,0,name_trn_id++);
  CVAL(outbuf,2) = (0x5<<3) | 0x1;
  CVAL(outbuf,3) = (1<<4) | 0x0;
  SSVAL(outbuf,4,1);
  SSVAL(outbuf,6,0);
  SSVAL(outbuf,8,0);
  SSVAL(outbuf,10,1);  
  p = outbuf+12;
  name_mangle(name,p);
  p += name_len(p);
  SSVAL(p,0,0x20);
  SSVAL(p,2,0x1);
  p += 4;
  CVAL(p,0) = 0xC0;
  CVAL(p,1) = 12;
  p += 2;
  SSVAL(p,0,0x20);
  SSVAL(p,2,0x1);
  SIVAL(p,4,myttl);
  SSVAL(p,8,6);
  CVAL(p,10) = nb_flags;
  CVAL(p,11) = 0;
  p += 12;
  memcpy(p,ip,4);
  p += 4;

  count = 3;
  while (count--)
    {
      Debug(2,"Sending reg request for %s at (%s)\n",name,inet_ntoa(*ip));


      show_nmb(outbuf);
      if (!send_packet(outbuf,nmb_len(outbuf),&bcast_ip,137,SOCK_DGRAM))
	return False;
      if (receive_nmb(inbuf,3))
	{
          int rec_name_trn_id = SVAL(inbuf,0);
	  int opcode = CVAL(inbuf,2) >> 3;
	  int nm_flags = ((CVAL(inbuf,2) & 0x7) << 4) + (CVAL(inbuf,3)>>4);
	  int rcode = CVAL(inbuf,3) & 0xF;

	  /* is it a negative response to our request? */
	  if ((rec_name_trn_id = name_trn_id) && 
              (opcode == 5 && nm_flags == 0x58 && rcode != 0 && rcode != 7))
	    {
	      char qname[100];
	      name_extract(inbuf,12,qname);
	      if (name_equal(qname,name))
		{
		  Debug(0,"Someone (%s) gave us a negative name regregistration response!\n",
			inet_ntoa(lastip));
		  return False;
		}
	    }	  
	  
	  /* it's something else - process it anyway, unless we are running
	   as a daemon. This is necessary as we may have been started by a 
	   name query of our name arriving on port 137 (often happens) */
	  if (!daemon)
	    {
	      show_nmb(inbuf);
	      construct_reply(inbuf,outbuf + nmb_len(outbuf));
	    }
	}
    }

  /* no negative replies, send a demand */
  p = outbuf;
  SSVAL(outbuf,0,name_trn_id++);
  CVAL(outbuf,2) = (0x5<<3);
  CVAL(outbuf,3) = (1<<4) | 0x0;
  SSVAL(outbuf,4,1);
  SSVAL(outbuf,6,0);
  SSVAL(outbuf,8,0);
  SSVAL(outbuf,10,1);  
  p = outbuf+12;
  name_mangle(name,p);
  p += name_len(p);
  SSVAL(p,0,0x20);
  SSVAL(p,2,0x1);
  p += 4;
  CVAL(p,0) = 0xC0;
  CVAL(p,1) = 12;
  p += 2;
  SSVAL(p,0,0x20);
  SSVAL(p,2,0x1);
  SIVAL(p,4,myttl);
  SSVAL(p,8,6);
  CVAL(p,10) = nb_flags;
  CVAL(p,11) = 0;
  p += 12;
  memcpy(p,ip,4);
  p += 4;
  
  Debug(2,"Sending reg demand for %s at (%s)\n",name,inet_ntoa(*ip));

  show_nmb(outbuf);
  return(send_packet(outbuf,nmb_len(outbuf),&bcast_ip,137,SOCK_DGRAM));
}


/****************************************************************************
reply to a reg request
****************************************************************************/
void reply_reg_request(char *inbuf,char *outbuf)
{
  int name_trn_id = SVAL(inbuf,0);
  char qname[100]="";
  int ttl;
  char *p = inbuf;
  struct in_addr ip;
  int n=0;
  unsigned char nb_flags;
  
  name_extract(inbuf,12,qname);

  p += 12;
  p += name_len(p);
  p += 4;
  p += name_len(p);
  p += 4;
  ttl = IVAL(p,0);
  nb_flags = CVAL(p,6);
  p += 8;
  memcpy(&ip,p,4);

  Debug(2,"Name registration request for %s (%s) nb_flags=0x%x\n",
	qname,inet_ntoa(ip),nb_flags);

  /* if the name doesn't exist yet then don't respond */
  if ((n = find_name(qname)) < 0)
    return;

  /* if it's a group name and being registered as a group then it's OK */
  if ((names[n].nb_flags & 0x80) && (nb_flags & 0x80))
    return;

  /* if it's not my name then don't worry about it */
  if (!name_equal(myname,qname))
    return;

  Debug(0,"Someones using my name (%s), sending negative reply\n",qname);

  /* Send a NEGATIVE REGISTRATION RESPONSE to protect our name */
  SSVAL(outbuf,0,name_trn_id);
  CVAL(outbuf,2) = (1<<7) | (0x5<<3) | 0x5;
  CVAL(outbuf,3) = (1<<7) | 0x6;
  SSVAL(outbuf,4,0);
  SSVAL(outbuf,6,1);
  SSVAL(outbuf,8,0);
  SSVAL(outbuf,10,0);  
  p = outbuf+12;
  strcpy(p,inbuf+12);
  p += name_len(p);
  SSVAL(p,0,0x20);
  SSVAL(p,2,0x1);
  SIVAL(p,4,names[n].ttl);
  SSVAL(p,8,6);
  CVAL(p,10) = nb_flags;
  CVAL(p,11) = 0;
  p += 12;

  memcpy(p,&ip,4);      /* IP address of the name's owner (that's us) */
  p += 4;

  if (ip_equal(&ip,&bcast_ip))
    {
      Debug(0,"Not replying to broadcast address\n");
      return;
    }

  show_nmb(outbuf);
  send_packet(outbuf,nmb_len(outbuf),&ip,137,SOCK_DGRAM);

  return;
}

/****************************************************************************
reply to a name query
****************************************************************************/
void reply_name_query(char *inbuf,char *outbuf)
{
  int name_trn_id = SVAL(inbuf,0);
  char qname[100]="";
  char *p = inbuf;
  unsigned char nb_flags = 0;
  struct in_addr tmpip;

  name_extract(inbuf,12,qname);

  /* if it's not my name then return */
  if (!name_equal(qname,myname))
    return;

  Debug(2,"(%s) querying my name (%s), sending positive reply\n",
	inet_ntoa(lastip), qname);
 
  /* Send a POSITIVE NAME QUERY RESPONSE to ack our presence */
  SSVAL(outbuf,0,name_trn_id);
  CVAL(outbuf,2) = (1<<7) | 0x5;
  CVAL(outbuf,3) = 0;
  SSVAL(outbuf,4,0);
  SSVAL(outbuf,6,1);
  SSVAL(outbuf,8,0);
  SSVAL(outbuf,10,0);  
  p = outbuf+12;
  strcpy(p,inbuf+12);
  p += name_len(p);
  SSVAL(p,0,0x20);
  SSVAL(p,2,0x1);
  SIVAL(p,4,myttl);
  SSVAL(p,8,6);
  CVAL(p,10) = nb_flags;
  CVAL(p,11) = 0;
  p += 12;
  memcpy(p,&myip,4);
  p += 4;

  show_nmb(outbuf);

  tmpip = lastip;
  send_packet(outbuf,nmb_len(outbuf),&tmpip,137,SOCK_DGRAM);

  return;
}


/****************************************************************************
  construct a reply to the incoming packet
****************************************************************************/
void construct_reply(char *inbuf,char *outbuf)
{
  int opcode = CVAL(inbuf,2) >> 3;
  int nm_flags = ((CVAL(inbuf,2) & 0x7) << 4) + (CVAL(inbuf,3)>>4);
  int rcode = CVAL(inbuf,3) & 0xF;
  
  if (opcode == 0x5 && (nm_flags & ~1) == 0x10 && rcode == 0)
    reply_reg_request(inbuf,outbuf);

  if (opcode == 0 && (nm_flags&~1) == 0x10 && rcode == 0)
    reply_name_query(inbuf,outbuf);
}


/****************************************************************************
  process commands from the client
****************************************************************************/
void process(char *lookup)
{
  static int trans_num = 0;
  
  InBuffer = (char *)malloc(BUFFER_SIZE);
  OutBuffer = (char *)malloc(BUFFER_SIZE);
  if ((InBuffer == NULL) || (OutBuffer == NULL)) 
    return;
  
  if (!reply_only && !register_name(InBuffer,OutBuffer,myname,&myip))
    {
      Debug(0,"Failed to register my own name\n");
      return;
    }
  
  if (*lookup)
    {
      struct in_addr ip;
      if (name_query(InBuffer,OutBuffer,lookup,&ip))
	printf("%s %s\n",inet_ntoa(ip),lookup);
      else
	printf("couldn't find name %s\n",lookup);
      return;
    }

  while (True)
    {
      if (!receive_nmb(InBuffer,0))	
	return;

      show_nmb(InBuffer);

      Debug(2,"%s Transaction %d\n",timestring(),trans_num);

      construct_reply(InBuffer,OutBuffer);

      trans_num++;
    }
}


/****************************************************************************
  open the socket communication
****************************************************************************/
BOOL open_sockets(BOOL daemon,int port)
{
  extern int Client;
  if (daemon)
    {
      struct hostent *hp;
      struct sockaddr_in sock;
      
      /* get host info */
      if ((hp = gethostbyname(myhostname)) == 0) 
	{
	  Debug(0, "Gethostbyname: Unknown host. %s\n",myhostname);
	  return False;
	}
  
      memset(&sock, 0, sizeof(sock));
      memcpy(&sock.sin_addr, hp->h_addr, hp->h_length);
      sock.sin_port = htons( port );
      sock.sin_family = hp->h_addrtype;
      sock.sin_addr.s_addr = INADDR_ANY;
      Client = socket(hp->h_addrtype, SOCK_DGRAM, 0);
      if (Client == -1) 
	{
	  perror("socket");
	  return False;
	}
  
      /* now we've got a socket - we need to bind it */
      while (bind(Client, (struct sockaddr * ) &sock,sizeof(sock)) < 0) 
	{
	  perror("bind");
	  close(Client);
	  return False;
	}
    }
  else
    {
      Client = 0;
    }
  /* We will abort gracefully when the client or remote system 
     goes away */
  signal(SIGPIPE, SIGNAL_CAST abort);
  return True;
}


/****************************************************************************
  initialise connect, service and file structs
****************************************************************************/
BOOL init_structs(void )
{
  int i = add_name();  
  struct hostent *hp;

  if (i < 0)
    return(False);

  strcpy(names[i].name,domain);
  memset(&names[i].ip,0,4);
  names[i].ttl = 0;
  names[i].nb_flags = 0x80;
  names[i].valid = True;
  
  /* get my host name */
  if (gethostname(myhostname, sizeof(myhostname)) == -1) 
    {
      Debug(0,"gethostname failed\n");
      return False;
    } 

  /* get host info */
  if ((hp = gethostbyname(myhostname)) == 0) 
    {
      Debug(0, "Gethostbyname: Unknown host %s.\n",myhostname);
      return False;
    }

  memcpy(&myip,hp->h_addr,4);

  /* Read the broadcast address from the interface */
  if (!got_bcast)
    get_broadcast(&myip,&bcast_ip);

  if (*myname == 0)
    strcpy(myname,myhostname);
  return True;
}

/****************************************************************************
usage on the program
****************************************************************************/
void usage(char *pname)
{
  printf("Usage: %s [-n name] [-B bcast address] [-D] [-p port] [-d debuglevel] [-l log basename]\n",pname);
  printf("\t-D                    become a daemon\n");
  printf("\t-P                    passive only. don't respond\n");
  printf("\t-R                    only reply to queries, don't actively send claims\n");
  printf("\t-p port               listen on the specified port\n");
  printf("\t-d debuglevel         set the debuglevel\n");
  printf("\t-l log basename.      Basename for log/debug files\n");
  printf("\t-n netbiosname.       the netbios name to advertise for this host\n");
  printf("\t-B broadcast address  the address to use for broadcasts\n");
  printf("\t-L name               lookup this netbios name then exit\n");
  printf("\n");
}


/****************************************************************************
  main program
****************************************************************************/
int main(int argc,char *argv[])
{
  int port = 137;
  int opt;
  extern FILE *dbf;
  extern int DEBUGLEVEL;
  extern char *optarg;
  pstring lookup = "";

  while ((opt = getopt (argc, argv, "L:B:Rn:l:d:Dp:hP")) != EOF)
    switch (opt)
      {
      case 'B':
	{
	  unsigned long a = inet_addr(optarg);
	  memcpy(&bcast_ip,&a,sizeof(a));
	  got_bcast = True;
	}
	break;
      case 'n':
	strcpy(myname,optarg);
	break;
      case 'P':
	{
	  extern BOOL passive;
	  passive = True;
	}
	break;
      case 'R':
	reply_only = True;
	break;
      case 'l':
	strcpy(debugf,optarg);
	break;
      case 'L':
	strcpy(lookup,optarg);
	break;
      case 'D':
	daemon = True;
	break;
      case 'd':
	DEBUGLEVEL = atoi(optarg);
	break;
      case 'p':
	port = atoi(optarg);
	break;
      case 'h':
	usage(argv[0]);
	exit(0);
	break;
      default:
	usage(argv[0]);
	exit(1);
      }

  
  /* NOTE: This is the opposite of the smbserver as name packets
     seem to use the opposite byte order to smb packets */
  NeedSwap = !big_endian();
  
  if (DEBUGLEVEL > 2)
    {
      extern FILE *login,*logout;
      pstring fname="";
      sprintf(fname,"%s.nmb.in",debugf);
      login = fopen(fname,"w"); 
      sprintf(fname,"%s.nmb.out",debugf);
      logout = fopen(fname,"w");
    }
  
  if (DEBUGLEVEL > 0)
    {
      pstring fname="";
      sprintf(fname,"%s.nmb.debug",debugf);      
      dbf = fopen(fname,"w");
      setbuf(dbf,NULL);
      Debug(1,"%s netbios nameserver version %s started\n",timestring(),VERSION);
      Debug(1,"Copyright Andrew Tridgell 1992,1993,1994\n");
    }
  
  (void)init_structs();

  if (daemon)
    {
      Debug(2,"%s becoming a daemon\n",timestring());
      become_daemon();
    }

  if (open_sockets(daemon,port))
    {
      process(lookup);
      close_sockets();
    }
  fclose(dbf);
  return(0);
}

#ifndef _LOADPARM_H
/* This is a dummy lp_keepalive() for the nameserver only */
int lp_keepalive()
{
return(0);
}
#endif
